import FungibleToken from "./FungibleToken.cdc"
import NonFungibleToken from "./NonFungibleToken.cdc"


pub contract NftEggsStore {

    pub event NftEggsStoreInitialized()

    pub event StorefrontInitialized(storefrontResourceID: UInt64)

    pub event StorefrontDestroyed(storefrontResourceID: UInt64)

    // 列表是否可用
    // 已创建列表并将其添加到店面资源中。
	// 此处的地址值在发出事件时有效，但他们所指的账户状态在可能会在NftEggsStore工作流外部发生变化，因此在使用它们时请仔细检查。
    //
    pub event ListingAvailable(
        storefrontAddress: Address,
        listingResourceID: UInt64,
        nftType: Type,
        nftID: UInt64,
        ftVaultType: Type,
        price: UFix64
    )

	//列表已完成
	//它要么被购买，要么被移除并销毁。
    //
    pub event ListingCompleted(listingResourceID: UInt64, storefrontResourceID: UInt64, purchased: Bool)

    pub let StorefrontStoragePath: StoragePath

    pub let StorefrontPublicPath: PublicPath


    // SaleCut
	//表示NFT出售付款时必须发送一定的收款人的结构
    pub struct SaleCut {
        // 对收款人的引用.
        pub let receiver: Capability<&{FungibleToken.Receiver}>

        // 将支付给收款人的付款金额。
        pub let amount: UFix64

        init(receiver: Capability<&{FungibleToken.Receiver}>, amount: UFix64) {
            self.receiver = receiver
            self.amount = amount
        }
    }


	//列表详细信息
	//包含列表数据的结构。
    //
    pub struct ListingDetails {
        // 存储列表的店面id
        pub var storefrontID: UInt64
        // 此列表是否已购买
        pub var purchased: Bool
        // 列表中NFT的类型
        pub let nftType: Type
        // 该类型中NFT的ID
        pub let nftID: UInt64
        // 付款的代币的类型。
        pub let salePaymentVaultType: Type
        // 支付的代币数量
        pub let salePrice: UFix64
        // 支付的代币在多个收款人中怎么分配（创建人收取版权费，交易平台收取佣金，剩下部分给原持有人）
        pub let saleCuts: [SaleCut]

        // 不可逆转地将此列表设置为已购买
        //
        access(contract) fun setToPurchased() {
            self.purchased = true
        }

        init (
            nftType: Type,
            nftID: UInt64,
            salePaymentVaultType: Type,
            saleCuts: [SaleCut],
            storefrontID: UInt64
        ) {
            self.storefrontID = storefrontID
            self.purchased = false
            self.nftType = nftType
            self.nftID = nftID
            self.salePaymentVaultType = salePaymentVaultType

            // Store the cuts
            assert(saleCuts.length > 0, message: "Listing must have at least one payment cut recipient")
            self.saleCuts = saleCuts

            // 计算出总价格
            var salePrice = 0.0
            // 对功能进行初步检查，并根据削减金额计算销售价格。
            for cut in self.saleCuts {

                cut.receiver.borrow()
                    ?? panic("Cannot borrow receiver")

                salePrice = salePrice + cut.amount
            }
            assert(salePrice > 0.0, message: "Listing must have non-zero price")

            // Store thsalePricee calculated sale price
            self.salePrice = salePrice
        }
    }


    // 为列表提供有用的公共接口的接口。
    //
    pub resource interface ListingPublic {
        // 对NFT的引用
        //
        pub fun borrowNFT(): &NonFungibleToken.NFT

		//购买
        //
        pub fun purchase(payment: @FungibleToken.Vault): @NonFungibleToken.NFT

        // getDetails
        //
        pub fun getDetails(): ListingDetails
    }


	//上市
    pub resource Listing: ListingPublic {
        // 销售的简单（非能力、非复杂）细节
        access(self) let details: ListingDetails

		//允许此资源从其集合中提取具有给定ID的NFT的功能。
		//此功能允许资源提取*任何*NFT，因此在提供时应小心
		//这样一种能力可以应用于资源，并始终检查其代码，以确保它将在未来以它声称的方式使用它
        access(contract) let nftProviderCapability: Capability<&{NonFungibleToken.Provider, NonFungibleToken.CollectionPublic}>

        // 对NFT的引用
        pub fun borrowNFT(): &NonFungibleToken.NFT {
            let ref = self.nftProviderCapability.borrow()!.borrowNFT(id: self.getDetails().nftID)

            assert(ref.isInstance(self.getDetails().nftType), message: "token has wrong type")
            assert(ref.id == self.getDetails().nftID, message: "token has wrong ID")
            return ref as &NonFungibleToken.NFT
        }

		//获取详细信息
		//以结构形式获取列表当前状态的详细信息。
		//这避免了为它们提供更多的公共变量和getter方法，并很好地使用脚本（不能返回资源）发挥了作用
        pub fun getDetails(): ListingDetails {
            return self.details
        }

		//购买
        pub fun purchase(payment: @FungibleToken.Vault): @NonFungibleToken.NFT {
            pre {
                self.details.purchased == false: "listing has already been purchased"
                payment.isInstance(self.details.salePaymentVaultType): "payment vault is not requested fungible token"
                payment.balance == self.details.salePrice: "payment vault does not contain requested price"
            }

            // 再次确认还没有被购买
            self.details.setToPurchased()

            // 获取NFT以转给购买者
            let nft <-self.nftProviderCapability.borrow()!.withdraw(withdrawID: self.details.nftID)
			//接收者和提供者都不可信，他们必须实现正确的接口，但除遵守其前置/后置条件外，它们可以不受限制的以任何给定的方式实现接口背后的功能。
			//因此我们不能信任接口后面的收集资源，我们必须检查它提供给我们的NFT资源，以确保它是正确的。
            assert(nft.isInstance(self.details.nftType), message: "withdrawn NFT is not of specified type")
            assert(nft.id == self.details.nftID, message: "withdrawn NFT does not have specified ID")

			//如果我们试图付款时没有任何接收人有效，我们将发送给第一个有效的接收者而不是中止交易，
			//因此，第一个接收人应当是卖方，或者是合同的约定接收人
            var residualReceiver: &{FungibleToken.Receiver}? = nil

            // 向每个收款人支付他们声明的付款金额
            for cut in self.details.saleCuts {
                if let receiver = cut.receiver.borrow() {
					let paymentCut <- payment.withdraw(amount: cut.amount)
                    receiver.deposit(from: <-paymentCut)
                    if (residualReceiver == nil) {
                        residualReceiver = receiver
                    }
                }
            }

            assert(residualReceiver != nil, message: "No valid payment receivers")

			//此时，如果所有接收者都处于活动状态且可用，则支付保险库将具有
			//剩下的令牌为零，从功能上讲，这将是一个消耗空保险库的无操作
            residualReceiver!.deposit(from: <-payment)
			//如果购买了该清单，我们将其视为在此完成。
			//否则我们认为它在析构函数中完成。
            emit ListingCompleted(
                listingResourceID: self.uuid,
                storefrontResourceID: self.details.storefrontID,
                purchased: self.details.purchased
            )

            return <-nft
        }

        // destructor
        //
        destroy () {
            // If the listing has not been purchased, we regard it as completed here.
            // Otherwise we regard it as completed in purchase().
            // This is because we destroy the listing in Storefront.removeListing()
            // or Storefront.cleanup() .
            // If we change this destructor, revisit those functions.
            if !self.details.purchased {
                emit ListingCompleted(
                    listingResourceID: self.uuid,
                    storefrontResourceID: self.details.storefrontID,
                    purchased: self.details.purchased
                )
            }
        }

        // initializer
        //
        init (
            nftProviderCapability: Capability<&{NonFungibleToken.Provider, NonFungibleToken.CollectionPublic}>,
            nftType: Type,
            nftID: UInt64,
            salePaymentVaultType: Type,
            saleCuts: [SaleCut],
            storefrontID: UInt64
        ) {
            // 存储销售信息
            self.details = ListingDetails(
                nftType: nftType,
                nftID: nftID,
                salePaymentVaultType: salePaymentVaultType,
                saleCuts: saleCuts,
                storefrontID: storefrontID
            )

            // 存储对NFT操作能力的引用
            self.nftProviderCapability = nftProviderCapability

			//检查提供程序是否包含NFT。
			//代币售出后，我们会再次检查。
			//我们不能将其移动到函数中，因为初始值设定项不能调用成员函数。
            let provider = self.nftProviderCapability.borrow()
            assert(provider != nil, message: "cannot borrow nftProviderCapability")

            // 检查NFT是否有效。
            let nft = provider!.borrowNFT(id: self.details.nftID)
            assert(nft.isInstance(self.details.nftType), message: "token is not of specified type")
            assert(nft.id == self.details.nftID, message: "token does not have specified ID")
        }
    }

	//店面管理资源
	//用于在店面中添加和删除列表
    pub resource interface StorefrontManager {
        // 创建销售列表项
        // 允许店面拥有者创建并插入信息到销售列表
        pub fun createListing(
            nftProviderCapability: Capability<&{NonFungibleToken.Provider, NonFungibleToken.CollectionPublic}>,
            nftType: Type,
            nftID: UInt64,
            salePaymentVaultType: Type,
            saleCuts: [SaleCut]
        ): UInt64
        // removeListing
        // 允许店面所有者删除任何销售清单
        pub fun removeListing(listingResourceID: UInt64)
    }



	//店面公众功能接口
    pub resource interface StorefrontPublic {
        pub fun getListingIDs(): [UInt64]
        pub fun borrowListing(listingResourceID: UInt64): &Listing{ListingPublic}?
        pub fun cleanup(listingResourceID: UInt64)
   }

	//店面
	//一种资源，允许其所有者管理列表，并允许任何人与列表交互
	//以便查询他们的详细信息并购买他们所代表的NFT。
    pub resource Storefront : StorefrontManager, StorefrontPublic {
        // 列出UUID的字典用于列出资源
        access(self) var listings: @{UInt64: Listing}

        // 创建销售列表项
        // 允许店面拥有者创建并插入信息到销售列表
         pub fun createListing(
            nftProviderCapability: Capability<&{NonFungibleToken.Provider, NonFungibleToken.CollectionPublic}>,
            nftType: Type,
            nftID: UInt64,
            salePaymentVaultType: Type,
            saleCuts: [SaleCut]
         ): UInt64 {
            let listing <- create Listing(
                nftProviderCapability: nftProviderCapability,
                nftType: nftType,
                nftID: nftID,
                salePaymentVaultType: salePaymentVaultType,
                saleCuts: saleCuts,
                storefrontID: self.uuid
            )

            let listingResourceID = listing.uuid
            let listingPrice = listing.getDetails().salePrice

            // Add the new listing to the dictionary.
            let oldListing <- self.listings[listingResourceID] <- listing
            // Note that oldListing will always be nil, but we have to handle it.
            destroy oldListing

            emit ListingAvailable(
                storefrontAddress: self.owner?.address!,
                listingResourceID: listingResourceID,
                nftType: nftType,
                nftID: nftID,
                ftVaultType: salePaymentVaultType,
                price: listingPrice
            )

            return listingResourceID
        }

        // removeListing
        // Remove a Listing that has not yet been purchased from the collection and destroy it.
        //
        pub fun removeListing(listingResourceID: UInt64) {
            let listing <- self.listings.remove(key: listingResourceID)
                ?? panic("missing Listing")
    
            // This will emit a ListingCompleted event.
            destroy listing
        }

        // getListingIDs
        // Returns an array of the Listing resource IDs that are in the collection
        //
        pub fun getListingIDs(): [UInt64] {
            return self.listings.keys
        }

        // borrowSaleItem
        // Returns a read-only view of the SaleItem for the given listingID if it is contained by this collection.
        //
        pub fun borrowListing(listingResourceID: UInt64): &Listing{ListingPublic}? {
            if self.listings[listingResourceID] != nil {
                return &self.listings[listingResourceID] as! &Listing{ListingPublic}
            } else {
                return nil
            }
        }

        // cleanup
        // 如果已经被购买，可以移除.
        // Anyone can call, but at present it only benefits the account owner to do so.
        // Kind purchasers can however call it if they like.
        //
        pub fun cleanup(listingResourceID: UInt64) {
            pre {
                self.listings[listingResourceID] != nil: "could not find listing with given id"
            }

            let listing <- self.listings.remove(key: listingResourceID)!
            assert(listing.getDetails().purchased == true, message: "listing is not purchased, only admin can remove")
            destroy listing
        }

        // destructor
        //
        destroy () {
            destroy self.listings

            // Let event consumers know that this storefront will no longer exist
            emit StorefrontDestroyed(storefrontResourceID: self.uuid)
        }

        // constructor
        //
        init () {
            self.listings <- {}

            // Let event consumers know that this storefront exists
            emit StorefrontInitialized(storefrontResourceID: self.uuid)
        }
    }

    // createStorefront
    // Make creating a Storefront publicly accessible.
    //
    pub fun createStorefront(): @Storefront {
        return <-create Storefront()
    }

    init () {
        self.StorefrontStoragePath = /storage/NftEggsStore
        self.StorefrontPublicPath = /public/NftEggsStore

        emit NftEggsStoreInitialized()
    }
}
